const Events = require("./Events");
const Player = require("./Player");
const Flow = require("./Flow");

const {
  redis_client,
  redis_prefix,
  md5,
  db, // firebase db
  dbPromise,
  adminEvents
} = require("../unit");
const ROOM_MAP = new class RoomMap extends Map {
  constructor(props) {
    super(props);
    this.room_number = 0;
  }
  set room_number(v) {
    if (v !== this._room_number) {
      this._room_number = v;
      adminEvents.emit("online-room-number-changed", v, this);
    }
  }
  get room_number() {
    return this._room_number;
  }
  set(key, room) {
    super.set(key, room);
    this.room_number = this.size;
  }
  delete(key) {
    super.delete(key);
    this.room_number = this.size;
  }
  clear() {
    super.clear();
    this.room_number = 0;
  }
}();
var acc_id = 1000;
const ROOM_IDS = [...new Array(10000).keys()].map(id =>
  ("000" + id).substr(-4)
);
const ROOM_STATUS = {
  UN_READY: 0, //"等待用户准备",
  READY: 1, //"所有用户已准备"
  GAMEING: 2, //"游戏中"
  COM_SCORES: 3, //"游戏尾期，统计分数与结果"
  SHOW_SCORES: 4 //"游戏尾期，显示结果"
};
const ROOM_SETTING = {
  max_round: 2, // 普通房间的回合数
  queue_max_round: 2, // 自由匹配的回合数
  bot_duration: 5 * 60 * 1000, // 5分钟后解散房间
  pre_room_use_card: 1 //开一个房间需要消耗的房卡
};
const PLAYER_ROOM_WEAKMAP = new WeakMap();
class Room extends Events {
  static getIns(room_id) {
    return ROOM_MAP.get(room_id);
  }

  constructor(player_number = 2, options = {}) {
    super();
    if (!ROOM_IDS.length) {
      throw new Error("无空闲房间");
    }
    // 随机房号： ROOM_IDS.splice(Math.floor(Math.random()*ROOM_IDS.length),1)[0]
    // 顺序房号： ROOM_IDS.shift()
    const room_id = (this.id = ROOM_IDS.splice(
      Math.floor(Math.random() * ROOM_IDS.length),
      1
    )[0]);
    ROOM_MAP.set(room_id, this);

    if (!(player_number === 2 || player_number === 3 || player_number === 4)) {
      throw new Error("房间人数有误");
    }

    const {
      mode = "消费建房" // or 自由匹配
    } = options;
    const max_round = options.hasOwnProperty("max_round")
      ? options.max_round
      : mode === "消费建房"
          ? ROOM_SETTING.max_round
          : mode === "自由匹配" ? ROOM_SETTING.queue_max_round : 2;

    this.hash_id = "";
    this.next_hash_id = "";
    this.reGenerateHashId(false);

    this.mode = mode;
    this.max_round = max_round;
    this.cur_round = this.max_round;

    this.flow_statistics_promise = null;
    this._recharge_lock = false;
    this.player_number = player_number;
    this.players = [];
    this.owner = null;
    this.status = ROOM_STATUS.UN_READY;
    this.data = null;
    const _player_outline_destory_room_wm = (this._player_outline_destory_room_wm = new WeakMap());
    const room = this;
    this._player_porp_changed = function(key, value) {
      const player = this;
      room.emitPropChanged("players");
      if (
        key === "status" &&
        value === Player.STATUS.UN_READY &&
        room.mode === "自由匹配" &&
        room.cur_round === 0
      ) {
        player.emit("socket-emit-leave-room", "房间可用回合已用完， 自动退出房间， 请重新排队", {
          only_self_leave: true
        });
      }
      if (key === "is_online") {
        if (value) {
          // 重新连接
          clearTimeout(_player_outline_destory_room_wm.get(player));
          _player_outline_destory_room_wm.delete(player);
        } else {
          // 玩家离线
          // 即便是在游戏准备区，也要强制踢出玩家。
          const bot_duration = ROOM_SETTING.bot_duration;
          const ti = setTimeout(() => {
            let punish_points;
            if (room.mode === "自由匹配") {
              punish_points = 12;
              const player_botimes = player.getBotimes();
              // 记录挂机信息
              player.addBotimesAndLog(Date.now() - bot_duration, {
                room: room.toJSON()
              });
              // 惩罚扣分
              if (player_botimes === 0) {
                //第一次少量惩罚
                punish_points = 6;
              }
              player.cutPoints(punish_points * 3);

              room.players.slice().forEach(other_player => {
                other_player.emit(
                  "socket-emit-leave-room",
                  `玩家${player.info.nickname}断线超过5分钟, 房间自动解散。`,
                  {
                    only_self_leave: true,
                    is_force_self_leave: true
                  }
                );
                if (punish_points && other_player !== player) {
                  other_player.addPoints(punish_points); // 将扣除的分数发给其它用户
                }
              });
            }
          }, bot_duration);
          _player_outline_destory_room_wm.set(player, ti);
        }
      }
    };
    this.on("playersChanged", players => {
      if (this.status === ROOM_STATUS.UN_READY) {
        if (
          players.length > 1 &&
          players.every(player => player.status === Player.STATUS.READY) &&
          players.length === this.player_number
        ) {
          this.reset(ROOM_STATUS.READY);
        }
      }
      if (this.status === ROOM_STATUS.GAMEING && this.flow) {
        if (
          players.length > 1 &&
          players.every(player => player.status === Player.STATUS.AFTER_SUBMIT)
        ) {
          this.reset(ROOM_STATUS.COM_SCORES);
        }
      }
    });
    this.flow = null;
    this.on("statusChanged", async status => {
      if (status === ROOM_STATUS.UN_READY) {
        if (this.mode === "自由匹配") {
          if (this.cur_round === 0) {
            // 自动充值
            console.log("房间可用回合已用完， 房间解散");
            this.players.slice().forEach(player => {
              player.emit("socket-emit-leave-room", "房间可用回合已用完， 房间解散， 请重新排队", {
                only_self_leave: true
              });
            });
          }
        }
      } else if (status === ROOM_STATUS.READY) {
        // 开始游戏了
        const now = Date.now();
        // 清除统计数据缓存
        this.flow_statistics_promise = null;
        if (this.mode === "消费建房") {
          if (this.cur_round === this.max_round && !this.flow) {
            // 正式开始第一回合，消耗房主的房卡
            await this.useOwnerCardsToStartGame();
          } else if (this.cur_round === 0) {
            // 自动充值
            await this.rechargeRoom();
          }
        }
        // 初始化日志数据
        if (this.cur_round === this.max_round) {
          if (this.next_hash_id) {
            this.hash_id = this.next_hash_id;
            this.emitPropChanged("hash_id");
          } else {
            this.reGenerateHashId();
          }
          console.log("初始化日志数据");
          const cur_room_info = Object.assign(
            {
              create_time: now
            },
            this.toJSON()
          );
          cur_room_info.players_json = JSON.stringify(cur_room_info.players);
          delete cur_room_info.players;
          cur_room_info.owner_json = JSON.stringify(cur_room_info.owner);
          delete cur_room_info.owner;
          delete cur_room_info.data;
          delete cur_room_info.cur_round;
          delete cur_room_info.status;
          db.ref("room_logs/" + this.hash_id).set(cur_room_info);

          // 记录到用户历史记录中
          const data = {
            room_id: this.hash_id,
            create_time: now
          };
          this.players.forEach(player => {
            if (player.id) {
              db.ref("user_logs/" + player.id).push(data);
            }
          });
        }
        const flow_id =
          "FLOW|" +
          this.hash_id.replace("ROOM|", "") +
          "|" +
          (this.max_round - this.cur_round);
        const flow = (this.flow = new Flow(this.players, flow_id, this));
        this.emit("game-start", flow);
        this.reset(ROOM_STATUS.GAMEING);
        for (let player of this.players) {
          // 玩家进入游戏状态
          player.reset(Player.STATUS.BEFORE_SUBMIT);
        }
        this.cur_round -= 1;
        this.emitPropChanged("cur_round");
        flow.on("end", res_data => {
          this.emit("end", {
            players: this.players.slice(), // 这里复制一份用户信息
            results_list: res_data
          });
          if (this.mode === "自由匹配") {
            // 根据结果修改用户水豆信息
            res_data.forEach(results => {
              const player = this.players.find(
                player => player.id === results.player_id
              );
              if (player) {
                player.addPoints(results.total);
              }
            });
          }
          console.log("牌局结束，保存当前牌局记录");
          const now = Date.now();

          // 记录当局游戏的结果
          db.ref("room_logs/" + this.hash_id + "/flows/" + flow_id).set({
            room_hash_id: this.hash_id,
            create_time: now,
            data_json: JSON.stringify(res_data)
          });
          // 生成下一场的hash_id
          this.reGenerateNextHashId();
        });
      } else if (status === ROOM_STATUS.COM_SCORES) {
        // 所有玩家已经提交摆牌结果，进行分数计算
        this.flow.calculateResults();
      }
    });

    this.on("player-submit-res", (player, res) => {
      if (this.status === ROOM_STATUS.GAMEING && this.flow) {
        // 玩家提交摆牌结果
        this.flow.playerSubmitRes(player, res);
        player.reset(Player.STATUS.AFTER_SUBMIT);
      }
    });
    // 游戏结束，使用end事件来统一处理，而不是等待摆牌结果，是考虑倒特殊牌的情况
    this.on("end", res => {
      this.reset(ROOM_STATUS.SHOW_SCORES, res);
    });
    this.on("*Changed", () => {
      adminEvents.emit("online-room-changed", this);
    });
  }
  reGenerateHashId(is_emit_changed = true) {
    this.hash_id =
      "ROOM|" + (Date.now() + Math.random()).toString(36).replace(".", "-");
    is_emit_changed && this.emitPropChanged("hash_id");
  }
  reGenerateNextHashId(is_emit_changed = true) {
    this.next_hash_id =
      "ROOM|" + (Date.now() + Math.random()).toString(36).replace(".", "-");
    is_emit_changed && this.emitPropChanged("next_hash_id");
  }
  reset(status, data = null) {
    this.status = status;
    this.emitPropChanged("status");
    if (data !== this.data) {
      this.data = data;
      this.emitPropChanged("data");
    }
  }
  restartFlow(player) {
    if (player) {
      if (this.players.includes(player)) {
        player.reset(Player.STATUS.UN_READY);
        if (!this.flow_statistics_promise) {
          // 开始请求统计数据
          const cache_player_map = new Map();
          const statistics_players = [];
          this.players.forEach(player => {
            const player_with_total = Object.assign(player.toJSON(), {
              total: 0
            });
            statistics_players.push(player_with_total);
            cache_player_map.set(player.id, player_with_total);
          });
          this.flow_statistics_promise = (async () => {
            const game_log = await dbPromise(
              db.ref("room_logs/" + this.hash_id)
            );
            // 整理比分数据
            for (let flow_id in game_log.flows) {
              const flow = game_log.flows[flow_id];
              const results_list = JSON.parse(flow.data_json);
              results_list.forEach(results => {
                cache_player_map.get(results.player_id).total += results.total;
              });
            }
            return statistics_players;
          })();
        }

        if (
          this.players.every(
            player =>
              player.status === Player.STATUS.UN_READY ||
              player.status === Player.STATUS.READY
          )
        ) {
          this.reset(Room.STATUS.UN_READY);
        }
      }
    } else {
      for (let player of this.players) {
        player.reset(Player.STATUS.UN_READY);
      }
      this.reset(Room.STATUS.UN_READY);
    }
  }
  addPlayer(player, can_be_owner = true) {
    const players = this.players;
    if (players.includes(player)) {
      return;
    }
    if (players.length >= this.player_number) {
      throw "房间已满";
    }
    if (!this.owner && can_be_owner) {
      this.owner = player;
      this.emitPropChanged("owner");
    }
    players.push(player);
    PLAYER_ROOM_WEAKMAP.set(player, this);

    player.on("*Changed", this._player_porp_changed);

    player.reset(Player.STATUS.UN_READY);
    this.emitPropChanged("players");
  }
  removePlayer(player, is_force) {
    const players = this.players;
    // 玩家必须不在游戏中才能离开
    if (!players.includes(player)) {
      console.log("玩家不属于这个房间，无法执行离开");
      return;
    }
    if (
      !is_force &&
      !(player.status === Player.STATUS.UN_READY ||
        player.status === Player.STATUS.READY)
    ) {
      console.log("玩家目前的游戏状态不允许离开房间");
      return;
    }
    players.splice(players.indexOf(player), 1);
    PLAYER_ROOM_WEAKMAP.delete(player);
    this._player_outline_destory_room_wm.delete(player);

    player.reset(); // 回到基本状态

    player.removeListener("*Changed", this._player_porp_changed);
    this.emitPropChanged("players");
    if (this.owner === player) {
      this.owner = null;
      this.emitPropChanged("owner");
    }
    if (!players.length) {
      this.destroy();
    }
    return true;
  }
  // 房间续费回合
  async rechargeRoom(player = this.owner) {
    if (!this.owner) {
      return;
    }
    if (this.cur_round !== 0) {
      throw "房间无需续费";
    }
    if (player.getCards() >= ROOM_SETTING.pre_room_use_card) {
      // 自动消耗房卡继续游戏
      this._recharge_lock = true;
      this.cur_round = this.max_round;
      this.emitPropChanged("cur_round");
      if(ROOM_SETTING.pre_room_use_card){
        await player.cutCards(ROOM_SETTING.pre_room_use_card);
      }
      if (player !== this.owner) {
        // 续费的人变成房主
        this.owner = player;
        this.emitPropChanged("owner");
      }
      this._recharge_lock = false;
    } else {
      this.owner.emit("socket-emit-leave-room", "房间可用回合已用完，房间解散");
    }
  }
  useOwnerCardsToStartGame() {
    this.cur_round = 0;
    return this.rechargeRoom();
  }
  destroy() {
    console.log("销毁房间", this.id);
    ROOM_MAP.delete(this.id);
    ROOM_IDS.push(this.id);
    this.players.slice().forEach(this.removePlayer.bind(this));
    this._player_outline_destory_room_wm = null;
  }
  toJSON() {
    return {
      id: this.id,
      mode: this.mode,
      hash_id: this.hash_id,
      next_hash_id: this.next_hash_id,
      player_number: this.player_number,
      players: this.players.map(player => {
        const player_json = player.toJSON();
        // 数据保护
        delete player_json.card_list;
        delete player_json.hold_cards;
        return player_json;
      }),
      max_round: this.max_round,
      cur_round: this.cur_round,
      owner: this.owner,
      status: this.status,
      data: this.data
    };
  }
}
Room.setting = ROOM_SETTING;
Room.STATUS = ROOM_STATUS;
Room.PLAYER_ROOM_WEAKMAP = PLAYER_ROOM_WEAKMAP;
Room.ROOM_MAP = ROOM_MAP;
module.exports = Room;
